package org.new9uy.tetris.game.player.ai;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

import org.new9uy.tetris.game.player.AbstractPlayer;
import org.new9uy.tetris.game.type.Game;
import org.new9uy.tetris.model.Matrix;
import org.new9uy.tetris.model.Mino;
import org.new9uy.tetris.model.tetrimino.*;

/*
 * TESTS :
 * 1) Resultat anterieur (if machin truc true)
 * 2) Calcul des trous
 * 3) Le plus bas possible
 * 4) Avec le Next
 * 5) (Random)
 */
public class Bot 
    extends AbstractPlayer
    implements Runnable
{
    private Difficulty difficulty;
    
    private Random rand;
    private ArrayList<Possibility> allPoss; //toutes les possibilite
    private boolean warning = false;
    private boolean stop = false;

    private static String getBotName(Difficulty difficulty)
    {
        return "Bot (" + difficulty.toString() + ")";
    }
    
    public Bot()
    {
        this(Difficulty.EASY);
    }
    
    public Bot(Difficulty difficulty) throws NullPointerException
    {
        this(0, difficulty);
    }
    
    public Bot(int startLvl, Difficulty difficulty) throws NullPointerException
    {
        super(getBotName(difficulty), startLvl, true);
        
        this.difficulty = difficulty;
        this.allPoss = null;

        rand = new Random();
    }

    public Difficulty getDifficulty()
    {
        return difficulty;
    }
    
    public int getDelayDisplace()
    {
        return getDifficulty().getDelayDisplace();
    }
    
    public int getDelayThink()
    {
        return getDifficulty().getDelayThink();
    }

    @Override
    public void run()
    {
        while (!stop && Game.current().isPlaying())
        {
            if (!getGravity().getPause() && getMatrix().nextHasSpawned())
            {
                try
                {
                    allPoss = allPossibilities();
                    findOptimalPlace();
                    Thread.sleep(getDelayThink());
                } catch (InterruptedException e)
                {
                    
                }
            }
        }
        
        //System.out.println("Bot stopped");
    }

    public void stop()
    {
        stop = true;
    }

    private void checkAbort(String msg) throws InterruptedException
    {
        if (stop)
        {
            throw new InterruptedException(msg + " aborted");
        }
    }

    private void findOptimalPlace() throws InterruptedException
    {
        //TODO
        //Eliminate "bad" choices:
        prevention();
        //Test 2:
        if (!warning)
        {
            leastGap();
            lowests();
        } else
        {
            lowests();
            leastGap();
        }
        //Test 3:


        //Test 5: (Random)
        int n = allPoss.size();
        Possibility decision = allPoss.get(rand.nextInt(n));

        if (difficulty == Difficulty.INSIDE || difficulty == Difficulty.HARD)
        {
            getMatrix().moveTetrimino('y', -1);
        }
        
        rotate(decision);
        move(decision);

        //decision.print();

        getGravity().drop();
    }

    private void rotate(Possibility decision) throws InterruptedException
    {
        Matrix mtx = getMatrix();
        while (mtx.currentOrientation() != decision.ori)
        {
            checkAbort("rotate");

            mtx.rotateTetrimino();
            try
            {
                Thread.sleep(getDelayDisplace());
            } 
            catch (InterruptedException e)
            {
                
            }
        }
    }

    private void move(Possibility decision) throws InterruptedException
    {
        Matrix mtx = getMatrix();

        boolean done = false;
        while (!done)
        {
            checkAbort("move");

            int x = mtx.currentLeftX();
            if (x == decision.x)
            {
                done = true;
            } else
            {
                if (x < decision.x)
                {
                    mtx.moveTetrimino('x', 1);
                } else
                {
                    mtx.moveTetrimino('x', -1);
                }
            }

            Thread.sleep(getDelayDisplace());
        }
    }

    //TEST 0:  Toutes les possibilites:
    private ArrayList<Possibility> allPossibilities() throws InterruptedException
    {
        //74 est le nombres maximal de possibilite
        ArrayList<Possibility> list = new ArrayList<>(74);

        Matrix mtx = getMatrix();
        Tetrimino mind = mtx.getBotMind();
        mtx.botReset();


        final int rots = mind.numRotates();

        for (int r = 0; r < rots; r++)
        {
            int n = maxNumShifts(mind);
            //if(maketetris) n--; TODO uncomment
            for (int x = 1; x <= n; x++)
            {
                checkAbort("calculation");

                Tetrimino ghost = new Tetrimino(new Mino[]{
					mtx.getMino(3,1),
					mtx.getMino(4,1),
					mtx.getMino(5,1), 
					mtx.getMino(6,1)}, Color.GRAY);
                
                Mino[] low = ghost.lowests();
                Possibility p = new Possibility(
                        mind.getOrientation(),
                        x,
                        ghost.mino(ghost.searchMino("MinY")).getY(),
                        mtx.countWholesBelow(low));
                list.add(p);
                mtx.botMove();
            }
            mtx.botRotate();
        }
        return list;
    }

    private void leastGap()
    {
        Iterator<Possibility> i = allPoss.iterator();
        int w = 21;
        while (i.hasNext())
        {
            Possibility p = i.next();
            if (p.wholes < w)
            {
                w = p.wholes;
            }
        }

        int j = 0;
        while (j < allPoss.size())
        {
            Possibility p = allPoss.get(j);
            if (p.wholes > w)
            {
                allPoss.remove(j);
            } else
            {
                j++;
            }
        }
    }

    private void lowests()
    {
        Iterator<Possibility> i = allPoss.iterator();
        int y = 21;
        while (i.hasNext())
        {
            Possibility p = i.next();
            if (p.y < y)
            {
                y = p.y;
            }
        }

        int j = 0;
        while (j < allPoss.size())
        {
            Possibility p = allPoss.get(j);
            if (p.y > y)
            {
                allPoss.remove(j);
            } else
            {
                j++;
            }
        }
    }

    private void prevention()
    {
        Matrix mtx = getMatrix();
        warning = false;
        //If at danger: (high blocks)
        for (int i = 20; i > 10; i--)
        {
            for (int j = 1; j < 11; j++)
            {
                if (!mtx.getMino(j, i).isEmpty())
                {
                    warning = true;
                    break;
                }
            }
        }
    }

    //Nombre d'endroits possibles ou on peut mettre t avec une telle
    //orientation.
    public static int maxNumShifts(Tetrimino t)
    {
        if (t instanceof L || t instanceof J || t instanceof T
                || t instanceof S || t instanceof Z)
        {
            //si le tet. est plat:
            if (t.getOrientation() % 2 == 0)
            {
                return 8;
            } //si il est debout:
            else
            {
                return 9;
            }
        } else
        {
            if (t instanceof I)
            {
                if (t.getOrientation() % 2 == 0)
                {
                    return 7;
                } else
                {
                    return 10;
                }
            } else
            {
                if (t instanceof O)
                {
                    return 9;
                } else
                {
                    throw new IllegalArgumentException(
                            "What the hell did you give me? This ain't a freaking Tetrimino.");
                }
            }
        }
    }

    private class Possibility
    {

        int ori; //orientation du tetrimino 
        int x; //Valeur x du refer() mino
        int y; //Valeur y du mino le plus bas
        int wholes; //nombres de trous cree.

        Possibility(int ori, int x, int y, int wholes)
        {
            this.ori = ori;
            this.x = x;
            this.y = y;
            this.wholes = wholes;
        }

        public void print()
        {
            System.out.println("Possibilty-- ori:" + ori + "  x:" + x + "  y:" + y + "  wholes:" + wholes);
        }
    }
}
